package com.huawei.java.main.optimization;

import com.huawei.java.main.client.ClientDemand;
import com.huawei.java.main.io.Writer;

import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

public class CleanOptimization {


    // clientDemand
    private final ClientDemand clientDemand;
    // 全局比较器
    private static final Comparator<Map.Entry<String, Integer>> valueComparator = Comparator.comparingInt(Map.Entry::getValue);
    private static final Comparator<Map.Entry<String, Double>> doubleComparator = Comparator.comparingDouble(Map.Entry::getValue);

    // 全局可提供流量能力
    private final Map<String, Integer> serverBandAbilityMap;

    // 判断此次是否分配成功,成功则无需二次分配，失败进入二次分配，结果写入clientServersSolution
    private int isScheduled(Map<String, Integer> serverBandAbilityMap, Integer demand, String clientName, Map<String, Integer> singleClientSolution) {
        if (demand == 0) {
            return 0;
        }
        //获得可以client对应的server
        HashMap<String, Integer> serversMap = new HashMap<>();
        // TODO: 2022/3/21 小流量大流量可选择
        long total = 0;
        // 每个client对应的server
        for (String serverName : clientDemand.getServerClientQos().getClientServerQosList().get(clientName)) {
            Integer integer = serverBandAbilityMap.get(serverName);
            serversMap.put(serverName, integer);
            total += integer;
        }
        // 按照值升序
        LinkedHashMap<String, Integer> linkedHashMap = valueUpSort(serversMap);
        ArrayList<Map.Entry<String, Integer>> serversList = new ArrayList<>(linkedHashMap.entrySet());
        //清空map
        serversMap.clear();
        linkedHashMap.clear();
        // 根据每个client能够获得的band总量，其中各个server的剩余占比能力来确定将demand分配到具体server的比例
        // 第一步计算占比:
        // 判断如果可用流量为0
        if (total == 0) {
            // 释放serversList
            serversList.clear();
            return (int) (demand - total);
        }

        int demandBackUp = demand;
        Double ratioDouble = getRatioDouble((double) demand, (double) total);

        // 判断可用流量小等于需求
        if(total < demand) {
            // 释放serversList
            serversList.clear();
            return (int) (demand - total);
        }
        else if (((total - demand) < total*0.1) || ratioDouble <= 0.001) {
            // 设置调度结果
            int i = 0;
            while (demand > 0) {
                if (demand <= serverBandAbilityMap.get(serversList.get(i).getKey())) {
                    // 单一服务器能力大于全部demand能力
                    serverBandAbilityMap.put(serversList.get(i).getKey(), serverBandAbilityMap.get(serversList.get(i).getKey()) - demand);
                    singleClientSolution.put(serversList.get(i).getKey(), demand);
                    break;
                }
                else {
                    int bandOrigin = serverBandAbilityMap.get(serversList.get(i).getKey());
                    demand -= serverBandAbilityMap.get(serversList.get(i).getKey());
                    serverBandAbilityMap.put(serversList.get(i).getKey(), 0);
                    if (singleClientSolution.get(serversList.get(i).getKey()) != null) {
                        // 取出原本分配值
                        bandOrigin = singleClientSolution.get(serversList.get(i).getKey());
                    }
                    singleClientSolution.put(serversList.get(i).getKey(), bandOrigin);
                    i ++;
                }
            }
            // 释放serversList
            serversList.clear();
            return 0;
        }
        else {
            // 总可用流量大于总需求流量
            // 计算分配比例
            // 剩余band值
            // TODO: 2022/3/22
            for (Map.Entry<String, Integer> entry : serversList) {
                Double proportion = getRatioDouble((double)entry.getValue(), (double)total);
                int clientProportion = (int) Math.round(proportion * 0.01 * demandBackUp);
                if (clientProportion == 0) {
                    continue;
                }

                // 分配数值精度解决
                if (demand <= clientProportion) {
                    int clientSolution = demand;
                    if (singleClientSolution.get(entry.getKey()) != null) {
                        clientSolution += singleClientSolution.get(entry.getKey());
                    }
                    singleClientSolution.put(entry.getKey(), clientSolution);
                    serverBandAbilityMap.put(entry.getKey(), entry.getValue()-demand);
                    demand = 0;
                    break;
                }

                if (entry.getValue() >= clientProportion) {
                    // 按比例分配
                    int clientSolution = clientProportion;
                    if (singleClientSolution.get(entry.getKey()) != null) {
                        clientSolution += singleClientSolution.get(entry.getKey());
                    }
                    // 设置单次的结果
                    singleClientSolution.put(entry.getKey(), clientSolution);
                    // 更新serverBandAbilityMap
                    serverBandAbilityMap.put(entry.getKey(), entry.getValue()-clientProportion);
                    demand -= clientProportion;
                }
                else {
                    // 全部分配
                    Integer integer = entry.getValue();
                    if (singleClientSolution.get(entry.getKey()) != null) {
                        integer += singleClientSolution.get(entry.getKey());
                    }
                    // 设置单次的结果
                    singleClientSolution.put(entry.getKey(), integer);
                    // 更新serverBandAbilityMap
                    serverBandAbilityMap.put(entry.getKey(), 0);
                    demand -= entry.getValue();
                }
            }
            if (demand != 0) {
                // 二次直接全部分配
                for (Map.Entry<String, Integer> entry : serversList) {
                    // 从全局中拿
                    if (serverBandAbilityMap.get(entry.getKey()) == 0) {
                        continue;
                    }

                    if (demand - serverBandAbilityMap.get(entry.getKey()) <= 0) {

                        int bandChange = serverBandAbilityMap.get(entry.getKey()) - demand;
                        int demandChange = demand;
                        if (singleClientSolution.get(entry.getKey())  != null) {
                            demandChange += singleClientSolution.get(entry.getKey());
                        }
                        serverBandAbilityMap.put(entry.getKey(), bandChange);
                        singleClientSolution.put(entry.getKey(), demandChange);
                        break;
                    }
                    else {
                        demand -= serverBandAbilityMap.get(entry.getKey());
                        int bandOrigin = serverBandAbilityMap.get(entry.getKey());
                        if (singleClientSolution.get(entry.getKey()) != null) {
                            // 取出原本分配值
                            bandOrigin = singleClientSolution.get(entry.getKey());
                        }
                        singleClientSolution.put(entry.getKey(), bandOrigin);
                        serverBandAbilityMap.put(entry.getKey(), 0);
                    }
                }

            }
            // 释放serversList
            serversList.clear();
            //分配成功，返回0
            return 0;
        }
    }

    private static LinkedHashMap<String, Integer> valueUpSort(Map<String, Integer> maps) {
        ArrayList<Map.Entry<String, Integer>> entries = new ArrayList<>(maps.entrySet());
        entries.sort(valueComparator);
        LinkedHashMap<String, Integer> objectObjectLinkedHashMap = new LinkedHashMap<>();
        for (Map.Entry<String, Integer> entry : entries) {
            objectObjectLinkedHashMap.put(entry.getKey(), entry.getValue());
        }
        // 空间释放
        entries.clear();
        return objectObjectLinkedHashMap;
    }

    public static Double getRatioDouble(double number, double denominator) {
        Double ratio = 0.000;
        if (denominator != 0) {
            DecimalFormat decimalFormat = new DecimalFormat("#0.00000");
            decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
            String ratioStr = decimalFormat.format(number / denominator);
            ratio = Double.parseDouble(ratioStr) * 100;
            //处理 可能出现小数点后大于2个的情况
            DecimalFormat df = new DecimalFormat("######0.00000");
            String format = df.format(ratio);
            ratio = Double.valueOf(format);
        }
        return ratio;
    }


    // 为每行分配，无负载算法
    public Map<String, Map<String, Integer>> setNormalLines(Integer[] maxClientDemand) {
        // TODO: 2022/3/22 优先分配占比高的数据
        // 动态serverBandAbilityMap
        Map<String, Integer> serverBandAbilityMap = new HashMap<>(this.serverBandAbilityMap);
        //每个客户端和他们对应server的分配
        Map<String, Map<String, Integer>> clientServersSolution = new HashMap<>(this.clientDemand.getClientName().length);
        // 创建客户端名称和demand基础Array映射
        HashMap<String, Double> clientNamePro = new HashMap<>(maxClientDemand.length);
        for (int i = 0; i < maxClientDemand.length; i ++) {
            long proportion = clientDemand.getClientBandWidth().get(clientDemand.getClientName()[i]);
            Double ratioDouble = getRatioDouble(maxClientDemand[i], proportion);
            clientNamePro.put(clientDemand.getClientName()[i], ratioDouble);
        }
        // 排序
        ArrayList<Map.Entry<String, Double>> dynamicDemands = new ArrayList<>(clientNamePro.entrySet());
        dynamicDemands.sort(doubleComparator);

        // 存放client对应demand
        HashMap<String, Integer> clientDemands = new HashMap<>(maxClientDemand.length);
        for (int i = 0; i < maxClientDemand.length; i ++) {
            clientDemands.put(clientDemand.getClientName()[i], maxClientDemand[i]);
        }

        for (int i = 0; i < maxClientDemand.length; i++) {
            //动态计算占比

            // 判断单台服务器承受能力，标记不能承受全部流量服务器
            // 每个客户端调度的结果存储,string-servernaem，integer-调度数量
            //返回调度结果，此时为空
            Map<String, Integer> eachClientSolution = new HashMap<>();
            //开始调度
            int scheduleSuccess = isScheduled(serverBandAbilityMap, clientDemands.get(dynamicDemands.get(i).getKey()), dynamicDemands.get(i).getKey(), eachClientSolution);
            // TODO: 2022/3/22 不能此时add

            if (scheduleSuccess == 0) {
                clientServersSolution.put(dynamicDemands.get(i).getKey(), eachClientSolution);
            }
            // 如果分配流量超载，取出之前已分配数据，二次分配
            if (scheduleSuccess > 0) {
                // 需要二次分配的流量
                int doubleScheduleSuccess = scheduleSuccess;
                // 需要被二次分配的节点,不变
                for (int j = i-1; j >= 0; j --) {
                    // 上一次没分配
                    if (clientServersSolution.get(dynamicDemands.get(j).getKey()) == null) {
                        continue;
                    }
                    // TODO: 2022/3/21 二次调度改变,只依赖上次
                    doubleScheduleSuccess = isDoubleScheduled(serverBandAbilityMap, doubleScheduleSuccess, dynamicDemands.get(j).getKey(), dynamicDemands.get(i).getKey(), clientServersSolution.get(dynamicDemands.get(j).getKey()));

                    // 计算第二次分配后前一个节点总和 判断是否相等
                    AtomicReference<Integer> value4 = new AtomicReference<>(0);
                    clientServersSolution.get(dynamicDemands.get(j).getKey()).forEach((k, v) -> {
                        value4.updateAndGet(v1 -> v1 + v);
                    });
                    System.out.println("第二次分配后:"+ dynamicDemands.get(j).getKey()+ ":" +value4.get());
                    // 计算分配总和 判断是否相等(结束)

//                    doubleScheduleSuccess = isScheduled(serverBandAbilityMap, doubleScheduleSuccess, clientName[j], clientServersSolution.get(j));
                    if (doubleScheduleSuccess == 0) {
                        // 更新当前client位置
                        i = j;
                        break;

                    }
                    // 更新到下一个客户端节点
                }
            }
            // 二次分配结束
            // 单台服务器承受能力
        }
        serverBandAbilityMap.clear();
        return clientServersSolution;
    }

    private Integer isDoubleScheduled(Map<String, Integer> serverBandAbilityMap, int demand, String clientNow, String clientNext, Map<String, Integer> clientScheduleNow) {
        if (demand == 0) {
            return 0;
        }
        System.out.println("二次调度前一个节点:"+clientNow+"     "+clientNext+"缺少"+demand);
        // 取得与下一个分配相关的server，查看当前client上减去公共server，还能存放多少
        Set<String> clientsNow = clientScheduleNow.keySet();
        Set<String> clientsNext = this.clientDemand.getServerClientQos().getClientServerQosList().get(clientNext);
        // 不相交集合
        ArrayList<String> similarityServers = new ArrayList<>();
        // 相交集合
        ArrayList<String> unSimilarityServers = new ArrayList<>();
        for (String now : clientsNow) {
            if (!clientsNext.contains(now)) {
                similarityServers.add(now);
            }

            if (clientsNext.contains(now)) {
                unSimilarityServers.add(now);
            }
        }

        // 判断是否有非重合节点
        if (similarityServers.size() == 0 || unSimilarityServers.size() == 0) {
            return demand;
        }

        // 计算总共还可以分配流量
        long sumBand = 0L;
        for (String similarityServer : similarityServers) {
            int tempBand = serverBandAbilityMap.get(similarityServer);
            sumBand += tempBand;
        }

        long sumBandBackUp = sumBand;
        int demandBackUp = demand;
        // 计算需要从clientSchedule中需要放回总流量
        long sumNeedBand = 0L;
        for (String unSimilarityServer : unSimilarityServers) {
            if (clientScheduleNow.get(unSimilarityServer) == null) {
                continue;
            }
            int tempBand = clientScheduleNow.get(unSimilarityServer);
            sumNeedBand += tempBand;
        }
        // 计算分配总和 判断是否相等
        // 计算分配总和 判断是否相等(结束)

        // 部分分配
        if (sumBand < demand) {
            for (String similarityServer : similarityServers) {
                int single = serverBandAbilityMap.get(similarityServer);
                demand -= single;
                serverBandAbilityMap.put(similarityServer, 0);
                if (clientScheduleNow.get(similarityServer) != null) {
                    single += clientScheduleNow.get(similarityServer);
                }
                clientScheduleNow.put(similarityServer, single);
            }

            // 流量归还
            // TODO: 2022/3/23 这里流量分配需要考虑最后值
            int need = 0;
            for (String unSimilarityServer : unSimilarityServers) {
                // 精度控制
                Double proportion = getRatioDouble((double)clientScheduleNow.get(unSimilarityServer), (double)sumNeedBand);
                int clientProportion = (int) Math.round(proportion * 0.01 * sumBandBackUp);
                serverBandAbilityMap.put(unSimilarityServer, serverBandAbilityMap.get(unSimilarityServer) + clientProportion);
                need += clientProportion;
                if (clientScheduleNow.get(unSimilarityServer) != null) {
                    clientProportion = clientScheduleNow.get(unSimilarityServer) - clientProportion;
                }
                clientScheduleNow.put(unSimilarityServer, clientProportion);
            }
            // 循环控制器
            int i = 0;
            while (need < sumBandBackUp) {
                // TODO: 2022/3/23 越界
                if (clientScheduleNow.get(unSimilarityServers.get(i)) >= (sumBandBackUp-need)) {
                    int temp = (int) (sumBandBackUp-need);
                    serverBandAbilityMap.put(unSimilarityServers.get(i), serverBandAbilityMap.get(unSimilarityServers.get(i)) + temp);
                    clientScheduleNow.put(unSimilarityServers.get(i), clientScheduleNow.get(unSimilarityServers.get(i)) - temp);
                    need += temp;
                    break;
                }
                need += clientScheduleNow.get(unSimilarityServers.get(i));
                serverBandAbilityMap.put(unSimilarityServers.get(i), serverBandAbilityMap.get(unSimilarityServers.get(i)) + clientScheduleNow.get(unSimilarityServers.get(i)));
                clientScheduleNow.put(unSimilarityServers.get(i), 0);
                i++;
            }

            // 分配不成功，返回缺少流量数
            return demand;
        }
        else if ((sumBandBackUp - demandBackUp) < sumBandBackUp*0.1) {
            // 设置调度结果
            for (String similarityServer : similarityServers) {
                int single = serverBandAbilityMap.get(similarityServer);
                if (single > demand) {
                    serverBandAbilityMap.put(similarityServer, single-demand);
                    if (clientScheduleNow.get(similarityServer) != null) {
                        demand += clientScheduleNow.get(similarityServer);
                    }
                    clientScheduleNow.put(similarityServer, demand);
                    break;
                }
                demand -= single;
                serverBandAbilityMap.put(similarityServer, 0);
                if (clientScheduleNow.get(similarityServer) != null) {
                    single += clientScheduleNow.get(similarityServer);
                }
                clientScheduleNow.put(similarityServer, single);
            }
            // 归还流量
            for (String unSimilarityServer : unSimilarityServers) {
                Double proportion = getRatioDouble((double)clientScheduleNow.get(unSimilarityServer), (double)sumNeedBand);
                int clientProportion = (int) Math.round(proportion * 0.01 * demandBackUp);
                // TODO: 2022/3/22 已修改
                serverBandAbilityMap.put(unSimilarityServer, serverBandAbilityMap.get(unSimilarityServer) + clientProportion);

                if (clientScheduleNow.get(unSimilarityServer) != null) {
                    clientProportion = clientScheduleNow.get(unSimilarityServer) - clientProportion;
                }
                clientScheduleNow.put(unSimilarityServer, clientProportion);
            }
            return 0;
        }
        // TODO:
        else {
            // 总可用流量大于总需求流量
            // 计算分配比例
            // 剩余band值
            // TODO: 2022/3/23 227扩大成了230
            int need = 0;
            for (String similarityServer : similarityServers) {
                Double proportion = getRatioDouble((double)serverBandAbilityMap.get(similarityServer), (double)sumBandBackUp);
                int clientProportion = (int) Math.round(proportion * 0.01 * demandBackUp);
                if (clientProportion > (demandBackUp-need)) {
                    serverBandAbilityMap.put(similarityServer, serverBandAbilityMap.get(similarityServer) - (demandBackUp-need));
                    clientScheduleNow.put(similarityServer, clientScheduleNow.get(similarityServer) + (demandBackUp-need));
                    need = demandBackUp;
                    break;
                }
                need += clientProportion;
                serverBandAbilityMap.put(similarityServer, serverBandAbilityMap.get(similarityServer) - clientProportion);
                clientScheduleNow.put(similarityServer, clientScheduleNow.get(similarityServer) + clientProportion);
            }
            // 循环控制器
            int i = 0;

            while (need < demandBackUp) {
                if (serverBandAbilityMap.get(similarityServers.get(i)) >= (demandBackUp-need)) {
                    int temp = (demandBackUp-need);
                    serverBandAbilityMap.put(similarityServers.get(i), serverBandAbilityMap.get(similarityServers.get(i)) - temp);
                    clientScheduleNow.put(similarityServers.get(i), clientScheduleNow.get(similarityServers.get(i)) + temp);
                    need += temp;
                    break;
                }
                need += serverBandAbilityMap.get(similarityServers.get(i));
                clientScheduleNow.put(similarityServers.get(i), serverBandAbilityMap.get(similarityServers.get(i)) + clientScheduleNow.get(similarityServers.get(i)));
                serverBandAbilityMap.put(similarityServers.get(i), 0);
                i++;
            }

            // 归还流量
            // TODO: 2022/3/23 back没有算完 221
            int back = 0;
            for (String unSimilarityServer : unSimilarityServers) {
                Double proportion = getRatioDouble((double)clientScheduleNow.get(unSimilarityServer), (double)sumNeedBand);
                int clientProportion = (int) Math.round(proportion * 0.01 * demandBackUp);
                if (clientProportion > (demandBackUp - back)) {
                    serverBandAbilityMap.put(unSimilarityServer, serverBandAbilityMap.get(unSimilarityServer) + ((demandBackUp-back)));
                    clientScheduleNow.put(unSimilarityServer, clientScheduleNow.get(unSimilarityServer) - ((demandBackUp-back)));
                    need = demandBackUp;
                    back += (demandBackUp - back);
                    break;
                }
                back += clientProportion;
                serverBandAbilityMap.put(unSimilarityServer, serverBandAbilityMap.get(unSimilarityServer) + clientProportion);
                clientScheduleNow.put(unSimilarityServer, clientScheduleNow.get(unSimilarityServer) - clientProportion);
            }
            // 循环控制器
            int j = 0;
            while (back < demandBackUp) {
                if (clientScheduleNow.get(unSimilarityServers.get(j)) >= (demandBackUp-back)) {
                    int temp = (demandBackUp-back);
                    serverBandAbilityMap.put(unSimilarityServers.get(j), serverBandAbilityMap.get(unSimilarityServers.get(j)) + temp);
                    clientScheduleNow.put(unSimilarityServers.get(j), clientScheduleNow.get(unSimilarityServers.get(j)) - temp);
                    back += temp;
                    break;
                }
                back += clientScheduleNow.get(unSimilarityServers.get(j));
                serverBandAbilityMap.put(unSimilarityServers.get(j), serverBandAbilityMap.get(unSimilarityServers.get(j)) + clientScheduleNow.get(unSimilarityServers.get(j)));
                clientScheduleNow.put(unSimilarityServers.get(j), 0);
                j++;
            }


            //分配成功，返回0
            return demandBackUp - need;
        }

    }


    //存放结果
    public CleanOptimization(ClientDemand clientDemand) {
        this.clientDemand = clientDemand;
        this.serverBandAbilityMap = this.clientDemand.getServerBandwidth().getServerBandMap();
    }

    public void sum() {
        ClientDemand clientDemand = this.clientDemand;
        List<String> strings = new ArrayList<String>();
        for (ClientDemand.EachDemand eachDemand : clientDemand.getAllClientDemand()) {
            // test

            Map<String, Map<String, Integer>> maps = setNormalLines(eachDemand.getEachClientDemand());
            maps.forEach((k, v) -> {
                StringBuilder solution = new StringBuilder();
                solution.append(k);
                solution.append(":");
                if (v.size() == 0) {
                    solution.append(":");
                }
                v.forEach((k1, v1) -> {
                    solution.append("<");
                    solution.append(k1);
                    solution.append(",");
                    solution.append(v1);
                    solution.append(">");
                    solution.append(",");
                });
                solution.deleteCharAt(solution.length() - 1);
                solution.append("\n");
                strings.add(solution.toString());
            });

            //释放内存
            maps.clear();
        }
        String s = strings.get(strings.size() - 1).trim();
        strings.remove(strings.size() - 1);
        strings.add(s);
        Writer.writeTxt(strings);
    }
}
